/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.compose

import com.android.flags.junit.FlagRule
import com.android.testutils.TestUtils.resolveWorkspacePath
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.gradle.project.build.invoker.GradleBuildInvoker
import com.android.tools.idea.gradle.project.build.invoker.GradleInvocationResult
import com.android.tools.idea.gradle.util.BuildMode
import com.android.tools.idea.rendering.StudioRenderService
import com.android.tools.idea.rendering.createNoSecurityRenderService
import com.android.tools.idea.testing.AgpVersionSoftwareEnvironmentDescriptor
import com.android.tools.idea.testing.AgpVersionSoftwareEnvironmentDescriptor.Companion.AGP_CURRENT
import com.android.tools.idea.testing.AndroidGradleProjectRule
import com.android.tools.idea.testing.NamedExternalResource
import com.android.tools.idea.testing.TestLoggerRule
import com.android.tools.idea.testing.buildAndWait
import com.android.tools.idea.testing.withCompileSdk
import com.android.tools.idea.testing.withTargetSdk
import com.android.tools.idea.uibuilder.visual.visuallint.VisualLintService
import com.android.tools.rendering.RenderService
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.project.Project
import com.intellij.testFramework.EdtRule
import com.intellij.testFramework.IndexingTestUtil
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import java.io.File
import org.junit.Assert.assertTrue
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

private const val COMPILE_SDK_VERSION = "35"

/**
 * [TestRule] that implements the [before] and [after] setup specific for Compose rendering tests.
 */
private class ComposeGradleProjectRuleImpl(
  private val projectPath: String,
  private val testDataPath: String,
  private val projectRule: AndroidGradleProjectRule,
) : NamedExternalResource() {
  override fun before(description: Description) {
    RenderService.shutdownRenderExecutor(5)
    RenderService.initializeRenderExecutor()
    StudioRenderService.setForTesting(projectRule.project, createNoSecurityRenderService())
    projectRule.fixture.testDataPath = resolveWorkspacePath(testDataPath).toString()
    projectRule.load(
      projectPath,
      AGP_CURRENT.withCompileSdk(COMPILE_SDK_VERSION).withTargetSdk(COMPILE_SDK_VERSION),
    )

    projectRule.invokeTasks("compileDebugSources").apply {
      buildError?.printStackTrace()
      assertTrue("The project must compile correctly for the test to pass", isBuildSuccessful)
    }

    IndexingTestUtil.waitUntilIndexesAreReady(projectRule.project)
    // Create VisualLintService early to avoid it being created at the time of project disposal
    VisualLintService.getInstance(projectRule.project)
  }

  override fun after(description: Description) {
    StudioRenderService.setForTesting(projectRule.project, null)
    RenderService.shutdownRenderExecutor(5)
  }
}

/**
 * A [TestRule] providing the same behaviour as [AndroidGradleProjectRule] but with the correct
 * setup for rendering Compose elements.
 */
open class ComposeGradleProjectRule(
  projectPath: String,
  testDataPath: String = TEST_DATA_PATH,
  private val projectRule: AndroidGradleProjectRule =
    AndroidGradleProjectRule(
      agpVersionSoftwareEnvironment =
        AgpVersionSoftwareEnvironmentDescriptor.AGP_CURRENT.withTargetSdk(COMPILE_SDK_VERSION)
          .withCompileSdk(COMPILE_SDK_VERSION)
    ),
) : ComposeProjectBasedTestRule {
  override val project: Project
    get() = projectRule.project

  override val fixture: CodeInsightTestFixture
    get() = projectRule.fixture

  protected open val delegate: RuleChain =
    RuleChain.outerRule(TestLoggerRule())
      .around(projectRule)
      .around(ComposeGradleProjectRuleImpl(projectPath, testDataPath, projectRule))
      .around(EdtRule())
      .around(FlagRule(StudioFlags.GRADLE_SAVE_LOG_TO_FILE, true))

  fun androidFacet(gradlePath: String) = projectRule.mainAndroidFacet(gradlePath)

  override fun apply(base: Statement, description: Description): Statement =
    delegate.apply(base, description)

  fun clean() = GradleBuildInvoker.getInstance(project).cleanProject()

  fun build(): GradleInvocationResult {
    val invocationResult =
      project.buildAndWait() { invoker ->
        invoker.executeTasks(
          GradleBuildInvoker.Request.builder(
              project,
              File(project.basePath!!),
              "compileDebugSources",
            )
            .setMode(BuildMode.COMPILE_JAVA)
            .build()
        )
      }
    invokeAndWaitIfNeeded { PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() }
    return invocationResult
  }

  fun buildAndAssertIsSuccessful() {
    build().also {
      it.buildError?.printStackTrace()
      assertTrue(it.isBuildSuccessful)
    }
  }

  fun requestSyncAndWait() {
    projectRule.requestSyncAndWait()
  }
}
